#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"

// .hpp header only 
// 只要包含头文件，都不用创建对象就可以直接使用，这就是单例模式！
// ThreadPool<task_t>::GetInstance()->Enqueue(Download);

namespace ThreadPoolModule
{
    using namespace ThreadModlue;
    using namespace LogModule;
    using namespace CondModule;
    using namespace MutexModule;

    static const int gnum = 5;
    template <typename T>
    class ThreadPool
    {
    private:
        void WakeUpAllThread()
        {
            LockGuard lockguard(_mutex);
            if (_sleepernum) // 有休眠的线程才广播  唤醒
                _cond.Broadcast(); // 广播唤醒所有的线程
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
        }

        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个休眠线程";
        }

        ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleepernum(0)
        {
            for (int i = 0; i < num; i++)
            {
                _threads.emplace_back(
                    [this]()
                    {
                        HandlerTask();
                    });
            }
        }
        void Start()
        {
            if (_isrunning) // 已经启动过了
                return;
            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
            }
        }

        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

    public:
        // 因为该函数为类的成员方法，所以在使用这个函数的时候需要现有对象，然后再调用该函数，这就很矛盾！！
        // 所以要加上static，static方法属于类，但是不依赖类，并且可以访问类中的静态变量，但是不能访问类内其他成员
        // 但是不影响，单例创建后就可以访问了
        static ThreadPool<T> *GetInstance() // 单例
        {
            // 再加一层if判断，这样就可以防止在已经有单例的情况下还申请锁，降低效率 
            // 通过双层判断的方式提高获取单例的效率
            if (inc == nullptr)  
            { 
                LockGuard lockguard(_lock);

                LOG(LogLevel::DEBUG) << "获取单例....";
                if (inc == nullptr)
                {
                    LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }

            return inc;
        }
        void Stop()
        {
            if (!_isrunning) // 已经停止了
                return;
            _isrunning = false;

            // 线程池退出的时候不应该直接暴力全部退出，而是等待任务队列中的任务执行完毕，再退出
            // 完全取完 && 取完后线程池整体设置为了_isrunning = false

            // 保证所有线程都可以走到HandlerTask()函数中， if (!_isrunning && _taskq.empty()) 退出 
            // 唤醒所有的线程  具体善后措施在HandlerTask()函数中注释讲解
            WakeUpAllThread();
        }
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }
        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T t;
                {
                    LockGuard lockguard(_mutex);

                    // 在while死循环里面运行的时候，即便把这个线程池设置为_isrunning = false，线程池也不知道，所以加上判断

                    // 1. a.队列为空 b. 线程池没有退出
                    /*
                        使用 _taskq.empty() && _isrunning 进行判断，为什么要有_isrunning判断？
                        1️⃣ 如果任务队列为空，但是在退出的时候线程被唤醒，就会卡在第一个while循环。
                            通过判断进程池已经被设置为false，
                            可以保证在后续(!_isrunning && _taskq.empty())判断中，该线程break，后退出
                        2️⃣ 如果任务队列不为空，第二个if判断中通过 _taskq.empty()判断就不会让线程退出
                            而是将任务队列种的任务继续执行，直到任务队列为空并且线程池运行状态也被设置为false
                        
                        这样就可以保证在线程池推出的时候，所有的任务都执行完毕，并且状态设置正确！
                        */
                    while (_taskq.empty() && _isrunning) 
                    {
                        _sleepernum++;
                        _cond.Wait(_mutex); // 放在循环中，防止虚假唤醒，只有当条件真正满足时才会执行后续
                        _sleepernum--;
                    }
                    // 2. 内部的线程被唤醒
                    if (!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }

                    // 一定有任务
                    t = _taskq.front(); // 从q中获取任务，任务已经是线程私有的了！！！
                    _taskq.pop();
                }
                t(); // 处理任务，需/要在临界区内部处理吗？1 0
                     // 任务的获取是原子的，但是获取后，整个任务是属于线程私有的，所以不需要在临界区中处理
            }
        }
        bool Enqueue(const T &in)
        {
            if (_isrunning)
            {
                LockGuard lockguard(_mutex);
                _taskq.push(in);

                /*
                    HandlerTask休眠逻辑是这样的：
                    while (_taskq.empty() && _isrunning) 
                    {
                        _sleepernum++;
                        _cond.Wait(_mutex); // 放在循环中，防止虚假唤醒，只有当条件真正满足时才会执行后续
                        _sleepernum--;
                    }

                    我们来想象一下当队列里有很多任务，并且有多个工作线程的情况：

                    假设有 5 个线程 (T1, T2, T3, T4, T5)，刚开始都睡着了 (_sleepernum == 5)。
                    任务 M1 来了，Enqueue(M1)。_threads.size() == _sleepernum 为真，WakeUpOne() 唤醒 T1。
                    T1 醒来，抢到 _mutex 锁。
                    T1 发现队列不空（有 M1），取出 M1，弹出队列，释放 _mutex。
                    T1 开始执行 M1 (在锁外，是并发的)。
                    紧接着，任务 M2, M3, M4, M5... 大量涌入队列。每次 Enqueue 时，_threads.size() == _sleepernum 都为假（因为 T1 醒着呢），所以 Enqueue 不再发信号。
                    现在，队列里有很多任务，T1 在执行 M1。
                    其他线程 (T2, T3, T4, T5) 呢？ 它们可能还在睡着（如果只有 T1 被唤醒了），或者有一些因为虚假唤醒、或者之前的其他信号而醒来了，正在等待获取 _mutex 锁。
                    T1 执行完 M1。它不会去睡觉，而是回到 HandlerTask() 的外层 while(true) 循环开头，立即尝试获取 _mutex 锁。
                    此时，尝试获取 _mutex 锁的可能包括：T1 (刚执行完 M1)、T2 (如果醒了)、T3 (如果醒了)...。这些线程都会竞争这把锁。
                    假设 T2 抢到了锁。T2 检查队列，发现里面有很多任务 (M2, M3, M4...)。T2 取出 M2，弹出队列，释放 _mutex。
                    T2 开始执行 M2 (并发)。
                    紧接着，T3 抢到了锁。T3 检查队列，取出 M3，弹出队列，释放 _mutex。
                    T3 开始执行 M3 (并发)。
                    同时，T1 执行完 M1 后，又抢到了锁。T1 检查队列，取出 M4，弹出队列，释放 _mutex。
                    T1 开始执行 M4 (并发)。
                    看到这里，您会发现，尽管只有在“所有线程都休眠”时才会通过 Enqueue 发出新的唤醒信号，
                    但一旦有线程活跃起来，它们会持续不断地检查任务队列并获取任务。而多个线程可以同时竞争锁来获取不同的任务，并在获取任务后同时执行它们。
                */
               // 也就是说，虽然只是唤醒一个，但是每一个线程在获取锁的时候是互斥的
               // 如果有多个任务的话，线程1获取任务后释放锁，然后执行获取的任务
               // 线程1执行任务同时，其他线程会开始竞争锁，获取后取得任务，然后执行
               // 其它线程继续这样，直到任务为空
               // 也就是说，线程池中的线程对获取的任务是并发执行的，但是获取任务是串行执行的
               // WakeUpOne(); 唤醒单个线程只是起到一个通知的作用
               // 也就是同步！有任务可以执行了，通知消费者可以开始消费了！
                if (_threads.size() == _sleepernum) 
                    WakeUpOne();
                return true;
            }
            return false;
        }
        ~ThreadPool()
        {
        }

    private:
        std::vector<Thread> _threads;
        int _num; // 线程池中，线程的个数
        std::queue<T> _taskq;
        Cond _cond;
        Mutex _mutex;

        bool _isrunning;
        int _sleepernum;  // 睡眠的线程个数

        // bug??
        static ThreadPool<T> *inc; // 单例指针
        static Mutex _lock;
    };

    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr; 

    template <typename T>
    Mutex ThreadPool<T>::_lock; // 类内如果有静态成员，需要类外初始化 对应上面单例指针的bug提示

}